package forum.server.controller;

import java.security.NoSuchAlgorithmException;
import java.util.Vector;

import forum.server.domain.*;
import forum.server.exceptions.*;
import forum.server.persistence.*;

/**
 * This class handles all users related operations.
 * Placed above the Domain layer. 
 * 
 * @author Mirsky
 *
 */
public class UserHandler {

	/**
	 * Stores currently logged user.
	 * If its' a guest session- null;
	 */
	private LoggedInUser loggedUser;

	/**
	 * Data persistent.
	 */
	private DataManipulatorInt dm;

	/**
	 * Logger.
	 */
	private static MyLogger logger = MyLogger.getInstance();
	

	/**
	 * Constructor.
	 * @param dm DataManipulator
	 * @param user LoggedInUser
	 */
	public UserHandler(DataManipulatorInt dm, LoggedInUser user) {
		this.loggedUser = user;
		this.dm = dm;
	}

	/**
	 * Adds a new user to the database.
	 * @param username A unique username
	 * @param pass A password
	 * @return instance of freshly created LoggedInUser
	 * @throws RegistrationException in case usrename already exists.
	 */
	public LoggedInUser register(String username, String pass) 
	throws RegistrationException, DatabaseException{
		LoggedInUser tUser = null;
		if (dm.isUserExists(username)) {
			logger.logInfo("Registration: Attempt to register with existing username: " + username);			
			throw new RegistrationException("Username already exists.");
		}
		try {
			tUser = new LoggedInUser(username, Security.encryptPassword(pass));
		} catch (NoSuchAlgorithmException e) {
			logger.logException("Password encryption error during registrartion.", e);
			System.exit(1); // TODO refactor??
		}
		logger.logInfo("Registration: Succefully registered a new user: " + username);
		dm.addUser(tUser);
		return tUser;
	}

	/**
	 * Login an existing user.
	 * @param username The username
	 * @param pass The password
	 * @return instance of LoggedInUser
	 * @throws NoSuchUserException in case username doesn't exist
	 * @throws IncorrectLoginException in case password incorrect
	 */
	public LoggedInUser login(String username, String pass) 
	throws IncorrectLoginException, NoSuchUserException, DatabaseException {
		LoggedInUser tUser = dm.getUser(username);
		if (tUser == null) {
			logger.logInfo("Login: Attempt to login with invalid username: " + username);	
			throw new NoSuchUserException("Invalid username.");
		}
		if (getConnectedRegisteredUsers().contains(username)) {
		   logger.logInfo("Login: Attempt to login with already logged useranme: " + username);
		   throw new IncorrectLoginException("User already logged in.");
		}
		if (tUser.validatePassword(pass)) {
			this.loggedUser = tUser;
			logger.logInfo("Login: Succefully logged in: " + username);
			this.dm.connectUser(username);
			this.dm.decGuestCount();
			return tUser;
		}
		else {
			logger.logInfo("Login: Attempt to login with invalid password: " + username);
			throw new IncorrectLoginException("Invalid password.");
		}
	}

	/**
	 * Logoffs currently logged user.
	 */
	public void logoff() {
		if (this.loggedUser != null) {
			logger.logInfo("Logoff: User has loggedoff: " + this.loggedUser.getUsername());
			try {
				this.dm.disconnectUser(this.loggedUser.getUsername());
				this.dm.incGuestCount();
			} catch (DatabaseException e) {
				logger.logSevere("Database error.");
			}
            this.loggedUser = null;
		}
	}
	
	public void logoff(boolean exitStatus) {
		try {
			System.out.println("guests: "+this.dm.getGuestCount());
		} catch (DatabaseException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		if (this.loggedUser != null) {
			logger.logInfo("Logoff: User has loggedoff: " + this.loggedUser.getUsername());
			try {
				this.dm.disconnectUser(this.loggedUser.getUsername());
				if (!exitStatus){
					this.dm.incGuestCount();
				}
			} catch (DatabaseException e) {
				logger.logSevere("Database error.");
			}
            this.loggedUser = null;
		} else{
			try {
				this.dm.decGuestCount();
			} catch (DatabaseException e) {
				logger.logSevere("Database error.");
			}
		}
	}
	/**
	 * Promote a regular user to Moderator.
	 * @param username The username of the lucky user
	 * @return instance of the freshly created Moderator
	 * @throws UnauthorizedOpException in case currently logged in user isn't Admin
	 * @throws NoSuchUserException in case useraname doesn't exist
	 */
	public Moderator promoteModerator(String username) 
	throws UnauthorizedOpException, NoSuchUserException, 
	NotLoggedInException, DatabaseException {
		Moderator tMod = null;
		if (this.loggedUser == null) {
			logger.logInfo("Admin: Tryin to promote while not logged in.");
			throw new NotLoggedInException("Must login as Admin to do that.");
		}
		if (!dm.isUserExists(username)) {
			logger.logInfo("Admin: Tryin to promote non existent user: " + username);
			throw new NoSuchUserException("No such uesrname exist.");
		}
		tMod = this.loggedUser.promoteModerator(dm.getUser(username));
		dm.promoteModerator(username);
		logger.logInfo("Admin: Succefully promoted: " + username);
		return tMod;
	}
	
	/**
	 * Promote a regular user to Moderator.
	 * @param username The username of the lucky user
	 * @return instance of the freshly created Moderator
	 * @throws UnauthorizedOpException in case currently logged in user isn't Admin
	 * @throws NoSuchUserException in case useraname doesn't exist
	 */
	public Moderator promoteModerator(String[] usernames) 
	throws UnauthorizedOpException, NoSuchUserException, 
	NotLoggedInException, DatabaseException {
		Moderator tMod = null;
		for(int i=0;i<usernames.length;i++){
			if (this.loggedUser == null) {
				logger.logInfo("Admin: Tryin to promote while not logged in.");
				throw new NotLoggedInException("Must login as Admin to do that.");
			}
			if (!dm.isUserExists(usernames[i])) {
				logger.logInfo("Admin: Tryin to promote non existent user: " + usernames[i]);
				throw new NoSuchUserException("No such uesrname exist.");
			}
			tMod = this.loggedUser.promoteModerator(dm.getUser(usernames[i]));
			dm.promoteModerator(usernames[i]);
			logger.logInfo("Admin: Succefully promoted: " + usernames[i]);
		}
		return tMod;
	}

	/**
	 * Demote a Moderator to regular user.
	 * @param username The useranme of the Moderator.
	 * @return instance of LoggedInUser
	 * @throws UnauthorizedOpException in case currently logged in user isn't Admin
	 * @throws NoSuchUserException in case username doesn't exist
	 */
	public LoggedInUser demoteModerator(String username) 
	throws UnauthorizedOpException, NoSuchUserException, 
	NotLoggedInException,  DatabaseException{
		LoggedInUser tUser;
		Moderator tMod;
		if (this.loggedUser == null) {
			logger.logInfo("Admin: Tryin to demote while not logged in.");
			throw new NotLoggedInException("Must login as Admin to do that.");
		}		
		if (!dm.isUserExists(username)) {
			logger.logInfo("Admin: Tryin to demote non existent user: " + username);
			throw new NoSuchUserException("No such uesrname exist.");
		}
		tMod = (Moderator)dm.getUser(username);	// TODO refactor to get rid of the casting
		tUser = this.loggedUser.demoteModerator(tMod);
		dm.demoteModerator(username);
		logger.logInfo("Admin: Succefully demoted: " + username);
		return tUser;
	}
	/**
	 * Demote a Moderator to regular user.
	 * @param username The useranme of the Moderator.
	 * @return instance of LoggedInUser the last user changed.
	 * @throws UnauthorizedOpException in case currently logged in user isn't Admin
	 * @throws NoSuchUserException in case username doesn't exist
	 */
	public LoggedInUser demoteModerator(String[] usernames) 
	throws UnauthorizedOpException, NoSuchUserException, 
	NotLoggedInException,  DatabaseException{
		LoggedInUser tUser=null;
		Moderator tMod=null;
		for(int i=0;i<usernames.length;i++){
			if (this.loggedUser == null) {
				logger.logInfo("Admin: Tryin to demote while not logged in.");
				throw new NotLoggedInException("Must login as Admin to do that.");
			}		
			if (!dm.isUserExists(usernames[i])) {
				logger.logInfo("Admin: Tryin to demote non existent user: " + usernames[i]);
				throw new NoSuchUserException("No such uesrname exist.");
			}
			tMod = (Moderator)dm.getUser(usernames[i]);
			tUser = this.loggedUser.demoteModerator(tMod);
			dm.demoteModerator(usernames[i]);
			logger.logInfo("Admin: Succefully demoted: " + usernames[i]);
		}
		return tUser;
	}
	/**
	 * 
	 * @return all the registered users.
	 * @throws DatabaseException when there is a problem with the database.
	 */
	public Vector<LoggedInUser> getAllUsers() throws DatabaseException{
		return this.dm.getAllUsers();
	}
	
	/** 
	 * @return all the registered users that can be promoted.
	 * @throws DatabaseException when there is a problem with the database.
	 */
	public Vector<LoggedInUser> getAllUsersForPromotion() throws DatabaseException{
		return this.dm.getAllUsersForPromotion();
	}
	
	/** 
	 * @return all the moderators.
	 * @throws DatabaseException when there is a problem with the database.
	 */
	public Vector<Moderator> getModerators() throws DatabaseException{
		return this.dm.getAllModerators();
	}
	
	/**
     * increases the count of the online guests. 
     * @throws DatabaseException on database error.
     */
    public void incGuestCount() throws DatabaseException{
            this.dm.incGuestCount();
    }
    /**
     * decreases the count of the online guests. 
     * @throws DatabaseException on database error.
     */
    public void decGuestCount() throws DatabaseException{
            this.dm.decGuestCount();
    }
    /**
     * gets the online guests count.
     * @throws DatabaseException on database error.
     * @return the guests count.
     */
    public int getGuestCount() throws DatabaseException{
            return this.dm.getGuestCount();
    }
    /**
 * save that the user is online.
 * @param username the username to connect
 * @throws DatabaseException on database error.
 */
    public void connectUser(String username) throws DatabaseException {
            this.dm.connectUser(username);
            this.dm.decGuestCount();
    }
    /**
 * save that the user is offline.
 * @param username the username to disconnect
 * @throws DatabaseException on database error.
 */
    public void disconnectUser(String username) throws DatabaseException{
            this.dm.disconnectUser(username);
            this.dm.incGuestCount();
    }
    /**
 * gets the currently connected users.
 * @return the connected users.
 * @throws DatabaseException on database error.
 */
    public Vector<String> getConnectedRegisteredUsers() throws DatabaseException{
            return this.dm.getConnectedRegisteredUsers();
    }

}
